iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 25
0
Modern Web

跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset系列 第 25

[day24] YDKJS (Objects) : 「this」 是 JavaScript 使用 Dynamic scope 的方法?

  • 分享至 

  • xImage
  •  

JavaScript 是 Objects(要加s) Oriented 和你想的 Object-oriented programming 不一樣

一般 Object-oriented programming (OOP) 使用 inheritance pattern,也就是大家熟悉的 Class 繼承。

JavaScript 的 Objects Oriented 屬於委託模式 (Delegation pattern)
特別要強調是來自於 Prototype-based programming 的觀念,
比較類似 Objects linked to other objects ( Objects 都是複數)。

Kyle 認為 委託模式 (Delegation pattern) 比 Class 繼承 更強大,
但很多人使用 JavaScript 都只有使用 Class 繼承,然後抱怨 JavaScript 很奇怪, Kyle 覺得很可惜。

「this」

不要拿其他語言的 this 來理解 JavaScript , JavaScript 自認很聰明,JavaScript 預判你的預判,你懂的。

重點在如何被呼叫

這個概念我們之前有提過:

[day20] YDKJS (Scope) : Advanced Scope
Dynamic scope : 很彈性,隨時會改變的 Scope

makes it more flexible reusable context ,這點和我們說的 Dynamic scope 就很像了。

這張圖也是同一篇有提到過,
當時候說 JavaScript 是靜態 Lexical Scope ,所以不能動態存取,
才把圖上面說 「Theoretical」。

但現在講到 this ,對比一下,是不是有點感覺了?

this 嘗試做一些 Dynamic scope 的能力,所以能 flexible reusable context

小整理目前已知的資訊 :

JavaScript 是靜態 Lexical Scope

  1. closure 是可以把執行 Lexical Scope 全部備份下來的能力。
    (否則 Lexical Scope 會把執行過的 function 回收)
  2. this 有可能是想要做到某些 Dynamic scope 的能力(透過function 什麼時候被呼叫 來決定內容)。
    (否則 Lexical Scope 在 compiler time 就決定好 scope ,沒什麼好疑惑)
  • this 版本:

    類似 Dynamic flexibility 的能力

this 強調是 動態的 如何被 invoked

而不像是 Lexical Scope 關注在可以被預測

之前我們提到,Lexical Scope 像是在建築物裡面,
從底部開始往上層找,直到 找到 或是到頂層global scope為止(左圖)。

但是 this 關注不同點, this 比較像是 「哪一棟是我要去的建築物?」

建築物可能是一個 function ,可能每一個 建築物(function) 都有 2 樓(都有一個變數 var teacher) 。
this 會觀察是哪個建築物,誰邀請他

  1. 如果是 Lexical Scope :
    知道是哪一棟建築物(一開始就有規劃書),從一樓開始往上找,看看什麼時候會找到,直到頂樓( global scope )為止。

  2. 如果是 this :
    知道目的地是二樓,但不知道要去哪一棟建築物的二樓。

JavaScript 有 4 種方法定義 this 如何被 invoked

(1.) implicit binging : 最常見的 this 使用方法。

這個和之前提到的 name space pattern 很像。
所以 this 在 name space pattern 會有什麼行為?
一樣,思考 function 什麼時候被呼叫

workshop.ask 的 . 是主要 invoke 的時候,
這個 workshop.ask 就是說要呼叫 workshop (Object) 裡面的的 ask() 方法,
所以存在的環境就是 workshop 這個 object,
所以這時候的建築物就是 workshop (Object)

workshop.ask 被呼叫,ask() 之中的 this 此時此刻 也就是代表 workshop (Object)

順帶一提,這也是大多數語言的 this 行為。
但是 JavaScript 還有其他 3 種方法,所以才容易混淆。

這種隱式綁定(implicit binding)的想法是很有用的,因為這樣我們可以在不同的 context 共享一種方法,不用每一次都寫固定的 Lexical Scope 。

這邊 定義一個 ask function,但是可以在兩種不同的 Objects (物件) 裡面共享方法,
因為 line 7, line 12 各有一個 reference 指向 ask function。

當我對 ask function 使用保留的 reference,
每一次 implicit binging 會找「誰」 invoke function,
所以在每個執行的時間點,都可以綁定不同的 context。

(2.) explicit binging

.call , .apply 很顯性的告訴 JavaScript,哪一個對象是我們要綁定的 this 。
第一個 argument 是要綁定的對象。

現在我們比較少直接用 explicit binging ,比較常用的情境是一種變體方法。

(2-1 .) explicit binging 的變體: hard binding

  • line 8
    這時候和 setTimeout 混用,綁定的東西可能跟之前不一樣了,會遺失我們要的 this 綁定,不好預測。
    (setTimeout 會回傳一個 callback function,此時真正綁定 this 的地方會在 cb[] a.k.a callback function 裡面

setTimeout 事實上會用 .call 來 explicitly invokes 一個 global context,
而不是直接呼叫 workshop.ask ,反而像是 透過 cb.call window(global context) 來呼叫 workshop.ask 。
都不理解也沒關係,不是這次的重點,只要知道這時候 setTimeout 綁不到你預測的 this

  • 解法 :line 11
    我們透過 .bind 方法,hard binding 強制使用傳入的參數。

.bing 並沒有 invoke function,比較像創造一個新的 function,把你特別指定的參數傳入 context 。

這樣又很像我們之前說的 Lexical Scope 方法所追求的 可預測性,有點浪費原本 this 綁定可以彈性的方法。
過於彈性就會難以預測,這也是寫 code 常常要思考的問題 : 可預測(過於靜態) 還是 彈性(不可預測)。

這是一種 trade-off ,魚與熊掌不可得兼。

Kyle 的建議是,他自己如果整份 code 偏向 flexible dynamism ,那就整份都用維持一致風格,如果偶爾需要使用 hard binding 確保程式可以預期,那是 ok 的,因為你從 flexible dynamism 上面獲得很多好處。

相反的,如果你整份都用 hard binding ,那你應該要思考是不是要使用 Lexical Scope(closure) 方法所追求的 可預測性 , 否則你只是繞遠路,多做很多不必要的事。

(3.) new binging

new keyword

使用 new keyword ,看起來好像是和 OOP 的 class constructot 有關,
其實只是故意做很像,但完全沒有關係。
可惜,在這層意義上(故意做很像)反而很多人誤會或誤解 new keyword 。

new keyword 有三種方法來 invoke 一個 function,其中又做 4 種特別的事,特別在你很難觀察它們的出現。

不過 new 的目標都是要 透過 this keyword 來 invoke function,放在一個 new empty object 裡面。

差異:

  1. 我們可以 invoke function 和動態指定 context object ,像我們之前 work.ask 的例子。
  2. 我們也可以透過 .call, .apply, .bind 傳入一個特定(指定)的 object 。
    ===
  3. 我們的 new binging 就是第三種方法,用一個 new empty object 來 invoke 一個 function。

仔細觀察,其實第二種方法( .call, .apply, .bind) 傳入的特定 Object ,如果是 {} (new empty object) 就是我們說的 new binding。

你可以想成,用 new keyword 是代表我要一個 new object 和 this content 來 invoke function。

Kyle 整理 new keyword 的行為

  1. 講過了
  2. 「Link」是 Kyle 的看法,因為 JavaScript 是 Prototype-based 來強調不是 OOP
  3. 重點是 call function 和 this 都會放到前面的 new object
  4. function 都要 return ,不過如果沒有指定, return 的預設會變成 this

其實如果全部講完 JavaScript 再回來看,這就是 new keyword 的行為,
不限於 invoke function。

(4.) default binding

如果不是以上三種方法,就是這種,所以叫做 default binding。
其實是一種錯誤使用 this 的 binding error 處理。
(我們前面提到,使用 this 的目的是 flexible dynamism)

line 12 : 沒有 context object 就呼叫 function

如果非嚴格模式 : global

你不會想看到這個 = ="
如果嚴格模式 : type error
default behavior is to leave this undefined,但你不能 access 一個屬性是 undefined value


了解 binding 的權重 : 瘋狂的範例

  • 數字代表優先程度 (數字 1 最優先)

Kyle ,你忘記 arrow function ?

Kyle : lexical (this) behavior
因為 arrow function 沒有定義 this keyword ,
所以你把 this 放到 arrow function ,其實等於放一個變數而已。

==> 那就是 lexical scope behavior ,往上層找。

line 5 的 arrow function 裡面有 this
但是 arrow function 不管 this ,就當作 lexical scope behavior ,往上層找什麼時候有 this keyword invoked,
(而且此時有 closure 保留 Execution Context )

所以 line 5 的外層 scope 是 ask function
this 就變成找 ask function 如何被 invoke。

Spec.:

arrow function 沒有定義 local binding

this + arrow

object is not scope,所以 this 會往外找到 global。
此時非嚴格模式,就變成 global 動態宣告 var teacher = undefined;

  • Kyle 說這個很多人會誤解的錯誤,恰好是他唯一使用 arrow function 的情境

因為可以讓 this 也做到 lexical scope 的能力。
你用正確的方法,就可以不用寫 var _this = this

Kyle

  1. 不要使用 arrow function 因為是匿名函式。
  2. 但如果你想要用 lexical scope + this ,使用 arrow function。

上一篇
[day23] YDKJS (Closure) : 從 Closure 到 Module Pattern
下一篇
[day25] YDKJS (Objects) : 真。淺談 this 與 Class, 到前端框架的愛恨情仇
系列文
跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言